﻿namespace Algorithm
{
    using System;

    public class Engine
    {
        // Debug
        //private static int totalDistance = 0;

        private readonly Board board;
        private readonly IInputReader inputReader;
        private readonly IOutputWriter outputWriter;
        private readonly ITile[] tiles;

        public Engine(Board board, IInputReader inputReader, IOutputWriter outputWriter)
        {
            this.board = board;
            this.inputReader = inputReader;
            this.outputWriter = outputWriter;
            this.tiles = new ITile[inputReader.LinesCount];
        }

        public void Run()
        {
            this.CreateTiles();
            this.MoveTiles();
            this.OutputResults();

            // Debug
            //Console.WriteLine(totalDistance);
        }

        private void CreateTiles()
        {
            for (int i = 0; i < this.inputReader.LinesCount; i++)
            {
                string[] tileInformation = this.inputReader.ReadLine();
                this.tiles[i] = this.CreateNewTile(tileInformation);

                Point2D[] points = this.tiles[i].GetAllPoints();
                for (int k = 0; k < points.Length; k++)
                {
                    this.board[points[k]]++;
                }
            }
        }

        private ITile CreateNewTile(string[] tileInformation)
        {
            string[] pointsDirections;
            int centralPositionRow = int.Parse(tileInformation[1]);
            int centralPositionCol = int.Parse(tileInformation[2]);
            Point2D centralPoint = new Point2D(centralPositionRow, centralPositionCol);

            switch (tileInformation[0])
            {
                case "ninetile":
                    pointsDirections = new[] { "u", "d", "l", "r", "ul", "ur", "dl", "dr" };
                    return new Tile(centralPoint, pointsDirections, 3);

                case "plus":
                    pointsDirections = new[] { "u", "d", "l", "r" };
                    return new Tile(centralPoint, pointsDirections, 2);

                case "hline":
                    pointsDirections = new[] { "l", "r" };
                    return new Tile(centralPoint, pointsDirections, 0);

                case "vline":
                    pointsDirections = new[] { "u", "d" };
                    return new Tile(centralPoint, pointsDirections, 0);

                case "angle-ur":
                    pointsDirections = new[] { "u", "r" };
                    return new Tile(centralPoint, pointsDirections, 1);

                case "angle-ul":
                    pointsDirections = new[] { "u", "l" };
                    return new Tile(centralPoint, pointsDirections, 1);

                case "angle-dr":
                    pointsDirections = new[] { "d", "r" };
                    return new Tile(centralPoint, pointsDirections, 1);

                case "angle-dl":
                    pointsDirections = new[] { "d", "l" };
                    return new Tile(centralPoint, pointsDirections, 1);

                default:
                    throw new ArgumentException(string.Format("Wrong tileInformation[0] {0}", tileInformation[0]));
            }
        }

        private void MoveTiles()
        {
            for (int currentMovePriority = 0; currentMovePriority < Tile.PriorityLevels; currentMovePriority++)
            {
                for (int i = 0; i < this.tiles.Length; i++)
                {
                    if (this.tiles[i].MovePriority == currentMovePriority)
                    {
                        this.MoveToNewPosition(this.tiles[i]);
                    }
                }
            }
        }

        private void MoveToNewPosition(ITile tile)
        {
            Point2D[] points = tile.GetAllPoints();
            bool moveIsNeeded = false;
            for (int i = 0; i < points.Length; i++)
            {
                if (this.board[points[i]] != 1)
                {
                    moveIsNeeded = true;
                    break;
                }
            }

            if (moveIsNeeded)
            {
                for (int i = 0; i < points.Length; i++)
                {
                    this.board[points[i]]--;
                }

                this.FindNewLocation(tile);
            }
        }

        private void FindNewLocation(ITile tile)
        {
            // Debug
            int currentDistance = 1;

            while (true)
            {
                int row = tile.CentralPoint.Row - currentDistance;
                int col = tile.CentralPoint.Col;

                for (int i = 0; i < currentDistance; i++)
                {
                    Point2D currentLocation = new Point2D(row, col);
                    if (this.LocationFound(currentLocation, tile))
                    {
                        // Debug
                        //totalDistance += currentDistance;

                        return;
                    }

                    row++;
                    col++;
                }

                for (int i = 0; i < currentDistance; i++)
                {
                    Point2D currentLocation = new Point2D(row, col);
                    if (this.LocationFound(currentLocation, tile))
                    {
                        // Debug
                        //totalDistance += currentDistance;

                        return;
                    }

                    row++;
                    col--;
                }

                for (int i = 0; i < currentDistance; i++)
                {
                    Point2D currentLocation = new Point2D(row, col);
                    if (this.LocationFound(currentLocation, tile))
                    {
                        // Debug
                        //totalDistance += currentDistance;

                        return;
                    }

                    row--;
                    col--;
                }

                for (int i = 0; i < currentDistance; i++)
                {
                    Point2D currentLocation = new Point2D(row, col);
                    if (this.LocationFound(currentLocation, tile))
                    {
                        // Debug
                        //totalDistance += currentDistance;

                        return;
                    }

                    row--;
                    col++;
                }

                currentDistance++;
            }
        }

        private bool LocationFound(Point2D currentLocation, ITile tile)
        {
            if (currentLocation.Row < 0 || currentLocation.Row >= this.board.Rows ||
                currentLocation.Col < 0 || currentLocation.Col >= this.board.Cols)
            {
                return false;
            }

            Point2D[] points = tile.GetAllPoints(currentLocation);

            for (int k = 0; k < points.Length; k++)
            {
                if (points[k].Row < 0 || points[k].Row >= this.board.Rows ||
                    points[k].Col < 0 || points[k].Col >= this.board.Cols)
                {
                    return false;
                }

                if (this.board[points[k]] != 0)
                {
                    return false;
                }
            }

            tile.CentralPoint = points[0];
            for (int i = 0; i < points.Length; i++)
            {
                this.board[points[i]]++;
            }

            return true;
        }

        private void OutputResults()
        {
            for (int i = 0; i < this.tiles.Length; i++)
            {
                this.outputWriter.WriteLine(this.tiles[i].CentralPoint);
            }
        }
    }
}